package slave;
/**
 *  @author alex
 *  Implementazione di un RTPConnector basato su una connessione Bluetooth L2CAP
 */
import java.io.IOException;
import java.util.EventObject;
import java.util.Vector;
import javax.bluetooth.L2CAPConnection;
import javax.media.protocol.PushSourceStream;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.SourceTransferHandler;
import javax.media.rtp.RTPConnector;
import javax.media.rtp.OutputDataStream;
import javax.microedition.io.Connector;
import javax.swing.event.EventListenerList;
import com.intel.bluetooth.BlueCoveImpl;
import commonUtility.ConnectionMonitor;
import commonUtility.L2capConnectionReference;
import slave.sspGUI.MonitorObservervable;
import slave.sspGUI.StreamProviderMonitor;

public class RTPL2capAdapterSender implements RTPConnector {
	/*Monitor per gestire la L2CAPConnection che deve essere condivisa*/
	ConnectionMonitor connectionDataMonitor ;
	ConnectionMonitor connectionCtrlMonitor ;

	/*Per effettuare uan connessione bluetooth*/
	L2capConnectionReference dataConnection;
	L2capConnectionReference ctrlConnection;

	/*Indirizzo mac per l'invio dei dati tramite bluetooth*/
	String urlL2CAPDataDestination;
	String urlL2CAPCtrlDestination;

	/*Thread per la gestione del PushSourceStream di lettura e dell'
	 * OutputDataStream di scrittura. */
	L2CAPInputStream dataInStrm = null, ctrlInStrm = null;
	L2CAPOutputStream dataOutStrm = null, ctrlOutStrm = null;
	
	/*Lista degli ascoltatori di un Adapter Event*/
	protected EventListenerList listenerList;
	
	/*Thread utilizzato per l'ascolto della nuova richista di SetMediaTime sul canale dati Gateway*/
	L2CAPInputDataStream l2capIdS;
	
	/**/
	String btAddress;
	ConnectionDataErrorEvent connErrEvt;
	/**Observer
	 * */
	private MonitorObservervable obs;
	private Vector <String> message = new Vector<String>();
	
	/**Stack bluetooth da utilizzare per i canali RTP.
	 * */
	private String stackBT;
	/**
	 * @param btAddressDestination : indirizzo mac di destinazione
	 * @param port : porta BT
	 * @param monitor TODO
	 * @param stackBT TODO
	 * @throws IOException
	 */
	public RTPL2capAdapterSender(String btAddressDestination, String port, StreamProviderMonitor monitor, String stackBT) throws IOException {
		
		listenerList = new EventListenerList(); 

		dataConnection = new L2capConnectionReference();
		ctrlConnection = new L2capConnectionReference();
		
		connectionDataMonitor = new ConnectionMonitor();
		connectionCtrlMonitor = new ConnectionMonitor();

		urlL2CAPDataDestination = "btl2cap://"+btAddressDestination+":"+ port+";ReceiveMTU=1024;TransmitMTU=1024";

		/*Incremento per la porta di controllo*/
		int portaTemporanea = (Integer.parseInt(port,16))+2;
		String ctrlPort=Integer.toHexString(portaTemporanea);
		btAddress=btAddressDestination;
		urlL2CAPCtrlDestination = "btl2cap://"+btAddressDestination+":"+ ctrlPort;
		connErrEvt = new ConnectionDataErrorEvent(this);
		this.stackBT=stackBT;
		obs = new MonitorObservervable(monitor);
	}

	/**
	 * Returns an input stream to receive the RTP data, che lato gateway utilizziamo 
	 * per gestire le richieste di TimeValue;
	 */
	public PushSourceStream getDataInputStream() throws IOException {
		if (dataInStrm == null) {
			dataInStrm = new L2CAPInputStream(dataConnection,connectionDataMonitor,true,this);
			dataInStrm.start();
		}
		return dataInStrm;
	}

	/**
	 * Returns an output stream to send the RTP data.
	 */
	public OutputDataStream getDataOutputStream() throws IOException {
		if (dataOutStrm == null)
			System.out.println("[RTPL2capAdapterSender]Send outputStream on:" +urlL2CAPDataDestination);
		dataOutStrm = new L2CAPOutputStream(dataConnection,connectionDataMonitor,urlL2CAPDataDestination,"RTP DATA");
		return dataOutStrm;
	}

	/**
	 * Returns an input stream to receive the RTCP data.
	 */
	public PushSourceStream getControlInputStream() throws IOException {
		if (ctrlInStrm == null) {
			ctrlInStrm = new L2CAPInputStream(ctrlConnection,connectionCtrlMonitor,false,this);
			ctrlInStrm.start();
		}
		return ctrlInStrm;
	}

	/**
	 * Returns an output stream to send the RTCP data.
	 */
	public OutputDataStream getControlOutputStream() throws IOException {
		if (ctrlOutStrm == null)
			ctrlOutStrm = new L2CAPOutputStream(ctrlConnection, connectionCtrlMonitor, urlL2CAPCtrlDestination, "RTP CONTROL");
		return ctrlOutStrm;
	}

	/**
	 * Close all the RTP, RTCP streams.
	 */
	public void close() {
		if(l2capIdS != null){
		l2capIdS.reset();
		}
		if (dataInStrm != null)
			dataInStrm.kill();
		if (ctrlInStrm != null)
			ctrlInStrm.kill();
		dataOutStrm.close();
		ctrlOutStrm.close();	
		System.out.println("Chiusi Stream DATA/CONTROL");
	}

	/**
	 * Set the receive buffer size of the RTP data channel.
	 * This is only a hint to the implementation.  The actual implementation
	 * may not be able to do anything to this.
	 */
	public void setReceiveBufferSize( int size) throws IOException {
		urlL2CAPDataDestination = urlL2CAPDataDestination + ";ReceiveMTU="+size;
		System.out.println("Chiamato ..setReceiveBufferSize");
	}

	/**
	 * Get the receive buffer size set on the RTP data channel.
	 * Return -1 if the receive buffer size is not applicable for 
	 * the implementation.
	 */
	public int getReceiveBufferSize() {
		System.out.println("Chiamato ..getReceiveBufferSize");

		try {
			if (dataConnection.getL2capConnection()==null){
				return L2CAPConnection.DEFAULT_MTU;
			}
			else return dataConnection.getL2capConnection().getReceiveMTU();
		}  catch (Exception e) {
			return -1;
		}
	}

	/**
	 * Set the send buffer size of the RTP data channel.
	 * This is only a hint to the implementation.  The actual implementation
	 * may not be able to do anything to this.
	 */
	public void setSendBufferSize( int size) throws IOException {
		System.out.println("Chiamato ..setSenderBufferSize");

		urlL2CAPDataDestination = urlL2CAPDataDestination + ";TransmitMTU="+size;
	}

	/**
	 * Get the send buffer size set on the RTP data channel.
	 * Return -1 if the send buffer size is not applicable for 
	 * the implementation.
	 */
	public int getSendBufferSize() {
		System.out.println("Chiamato ..getSenderBufferSize");

		try {
			if (dataConnection.getL2capConnection()==null){
				return L2CAPConnection.MINIMUM_MTU;
			}
			else return dataConnection.getL2capConnection().getTransmitMTU();
		} catch (Exception e) {
			return -1;
		}
	}

	/**
	 * Return the RTCP bandwidth fraction.  This value is used to
	 * initialize the RTPManager.  Check RTPManager for more details.
	 * Return -1 to use the default values.
	 */
	public double getRTCPBandwidthFraction() {
		return -1;
	}

	/**
	 * Return the RTCP sender bandwidth fraction.  This value is used to
	 * initialize the RTPManager.  Check RTPManager for more details.
	 * Return -1 to use the default values.
	 */
	public double getRTCPSenderBandwidthFraction() {
		return -1;
	}


	/**
	 * An inner class to implement an OutputDataStream based on UDP sockets.
	 */
	class L2CAPOutputStream implements OutputDataStream {

		L2capConnectionReference l2capConnection;
		ConnectionMonitor connectionMonitor;
		String urlL2CAPdestination;
		String canale;
		int i=0;
		int open_e=0;

		public L2CAPOutputStream(L2capConnectionReference l2capConnection,ConnectionMonitor connectionMonitor,String urlL2CAPSdestination,String canale) {
			this.l2capConnection = l2capConnection;
			this.connectionMonitor = connectionMonitor;
			this.urlL2CAPdestination = urlL2CAPSdestination;
			this.canale = canale;


		}

		public int write(byte data[], int offset, int len) {
		//Metodi per la gestione dello stack bluetooth tra + thred	
		BlueCoveImpl.useThreadLocalBluetoothStack();		
		BlueCoveImpl.setThreadBluetoothStackID(BluetoothStack.getInstance().getId(stackBT));		
			if(l2capConnection.getL2capConnection()==null){
			try {
				l2capConnection.setL2capConnection((L2CAPConnection) Connector.open(urlL2CAPdestination));
				System.out.println("[RTPL2capAdapterSender]"+canale + ": Open write in Outputstream");
			} catch (Exception e) {	
				System.out.println("Current Thread "+Thread.currentThread());
				open_e++;
				System.out.println("[RTPL2capAdapterSender]"+canale+ ": "+open_e+" eccezione nella open verso "+btAddress);	
				if(open_e==10){
				if(!message.isEmpty()){
					message.clear();
					}
				message.add(0,"EVT");
				message.add(1,"ADPT: "+btAddress+" open "+canale+" FAILURE");
				obs.notify(message);
				fireConnectionDataErrorEvent(connErrEvt);	
				}
				return -1;
			}
			if(connectionMonitor!=null){
				connectionMonitor.setConnectionOpened(true);
				System.out.println("[RTPL2capAdapterSender]"+canale+ ": Monitor setto aperta in write..");
				synchronized(connectionMonitor){
					connectionMonitor.notify();
				}
			}
			}	
		        try { 
		        	l2capConnection.getL2capConnection().send(data);
			} catch (Exception e) {
				i++;
				
            /**
             * Dovr� essere possibile gestire le eccezioni di Invio.
             * Utilizzer� un evento che avvertirò il Transmith dell'eccezione.
             * Tentavi di write stabiliti da statistiche.
             */
				if(i>200){
					if(!message.isEmpty()){
						message.clear();
						}
					message.add(0,"EVT");
					message.add(1,"ADPT: "+btAddress+" connection "+canale+" send FAILURE");
					obs.notify(message);
			    fireConnectionDataErrorEvent(connErrEvt);
				System.out.println("[EVT] [RTPL2capAdapterSender]"+canale+": Lanciato ConnectionDataErrorEvent!!");
				}
				return -1;//Host non presente/pronto sospendere l'invio.
			}
			return len;
		}

		public void close(){
			if(l2capConnection.getL2capConnection()!=null){	
				try {
					l2capConnection.getL2capConnection().close();
				} catch (IOException e) {
					e.printStackTrace();
				}		
			}
		}
	}

	/**
	 * An inner class to implement an PushSourceStream based on l2cap BT.
	 */
	class L2CAPInputStream extends Thread implements PushSourceStream {

		L2capConnectionReference l2capConnection;
		ConnectionMonitor connectionMonitor;
		RTPL2capAdapterSender adapterSender;
		boolean done = false;
		boolean dataRead = false;
		boolean dataConnection;
		boolean actived= false;


		SourceTransferHandler sth = null;

		public L2CAPInputStream(L2capConnectionReference l2capConnection, ConnectionMonitor connectionMonitor, boolean dataConnection,RTPL2capAdapterSender adapterSender) {
			this.l2capConnection = l2capConnection;
			this.connectionMonitor = connectionMonitor;
			this.dataConnection = dataConnection;
			this.adapterSender = adapterSender;


		}

		public int read(byte buffer[], int offset, int length) {
			if(!connectionMonitor.getConnectionOpened())
				synchronized(connectionMonitor){
					while(!connectionMonitor.getConnectionOpened()){
						try{
							connectionMonitor.wait();	
							if(l2capConnection.getL2capConnection()==null)System.out.println("sbloccato con l2capconn nulla");

						}	
						catch (InterruptedException e) {
							e.printStackTrace();
						}
					}	
				} 
			int byteReceived = 0;
			/*Se non sono una connessione Dati mi metto in ricezione, altrimenti ritorno Zero.*/
			if(!dataConnection){
			try {
				byteReceived = l2capConnection.getL2capConnection().receive(buffer);
			} catch (Exception e) {
				System.out.println("[RTPL2capAdapterSender]L2CAPInputStream: getL2capConnection().receive(buffer) Exception");
			return -1;
			}
			
			synchronized (this) {
				dataRead = true;
				notify();
			}												
			}
			if(!actived && dataConnection){
		    l2capIdS = new L2CAPInputDataStream(l2capConnection,adapterSender);
			l2capIdS.start();//Metto in ascolto il thread sul canale dati.
			System.err.println("[RTPL2capAdapterSender]L2CAPInputStream: Activated Change Time Thread");
			actived = true; // Setto actived true in modo da non ricreare ad ogni read un nuovo Thread.
			}
			return byteReceived;
		}
		

		public synchronized void start() {
			super.start();
			if (sth != null) {
				dataRead = true;
				notify();
			}
		}

		public synchronized void kill() {
			done = true;
			notify();
		}

		public int getMinimumTransferSize() {
			return 2 * 1024;	// twice the MTU size, just to be safe.
		}

		public synchronized void setTransferHandler(SourceTransferHandler sth) {
			this.sth = sth;
			dataRead = true;
			notify();
		}

		// Not applicable.
		public ContentDescriptor getContentDescriptor() {
			return null;
		}

		// Not applicable.
		public long getContentLength() {
			return LENGTH_UNKNOWN;
		}

		// Not applicable.
		public boolean endOfStream() {
			return false;
		}

		// Not applicable.
		public Object[] getControls() {
			return new Object[0];
		}

		// Not applicable.
		public Object getControl(String type) {
			return null;
		}			
		/**
		 * Loop and notify the transfer handler of new data.
		 */
		public void run() {
			while (!done) {

				synchronized (this) {
					while (!dataRead && !done) {
						try {
							wait();
						} catch (InterruptedException e) { }
					}
					dataRead = false;
				}

				if (sth != null && !done) {
					sth.transferData(this);
				}
			}
		}
	}
	
	
    /****************************************************************
     * NEW Inner Classes *******************************************
     ****************************************************************/

	/**
	 * Questa nuovo thread sara'utilizzato per mettersi in ascolto sul canale dati
	 * del nuovo media time richiesto dal client in ricezione.
	 * @author alex
	 */
	
	
	class L2CAPInputDataStream extends Thread {
	
	RTPL2capAdapterSender adapterSender;
	GetNewTimeValueEvent timeEvt;
    L2capConnectionReference l2capConnection;
    boolean runner=true;
	
	public L2CAPInputDataStream (L2capConnectionReference l2capConnection,RTPL2capAdapterSender adapterSender){
	this.l2capConnection = l2capConnection;
	this.adapterSender = adapterSender;
	}		
	public void run(){
	try{	
		while(runner){  
			   byte buffer[] = new byte[10];
			   int byteReceived = 0;
			try {
				byteReceived = l2capConnection.getL2capConnection().receive(buffer);
			} catch (IOException e) {
				System.out.println("[RTPL2capAdapterSender]L2CAPInputDataStream(Time Thread): getL2capConnection().receive(buffer) Exception");
				runner=false;
			}	          
			if(byteReceived!=0){
	            String received = new String(buffer, 0, byteReceived);
	            /*GENERO UN NUOVO EVENTO*/
	        	timeEvt = new GetNewTimeValueEvent(adapterSender,received);    
				fireGetNewTimeValueEvent(timeEvt);
	            System.err.println("[EVT] [RTPL2capAdapterSender]Data inputstream received new time: "+received);
			}
		}//Fine While
		
	}
	catch(Exception e){
		
	}
			}
	public void reset(){
    System.err.println("[RTPL2capAdapterSender]Closed set time handler thread");
	runner=false;
	}
	
	}
	
	/**********************************************************************************************
	 * METODI PER LA REALIZZAZIONE DI EVENTI LEGATI a: RTPL2capAdapterSender
	 **********************************************************************************************/

	
    @SuppressWarnings("serial")
	public class  GetNewTimeValueEvent extends EventObject {
    	String timeValue = null;
        public  GetNewTimeValueEvent(RTPL2capAdapterSender source,String timeValue) {
            super(source);
            this.timeValue=timeValue;
        }
        
        public String getTime(){    	
			return timeValue;      	
        }
        
    }
   
    /**
     * @author EVENTO CHE NOTIFICA UNA ECCEZIONE IN LETTURA/SCRITTURA SULLA CONNESSIONE L2CAP.
     */
    
	@SuppressWarnings("serial")
	public class  ConnectionDataErrorEvent extends EventObject {
        public  ConnectionDataErrorEvent(RTPL2capAdapterSender source) {
       	super(source);	   
        }
    }

    
	/**
	 * Questi metodi saranno utilizzati per aggiungere/rimuovere ascoltatori e lanciare gli eventi.
	 */
    public void addRTPAdapterSenderListener(RTPL2capAdapterSenderListener listener) {
        listenerList.add(RTPL2capAdapterSenderListener.class, listener);
    }

    public void removeRTPAdapterSenderListener(RTPL2capAdapterSenderListener listener) {
        listenerList.remove(RTPL2capAdapterSenderListener.class, listener);
    }

    void fireConnectionDataErrorEvent(ConnectionDataErrorEvent evt) {
        Object[] listeners = listenerList.getListenerList();
        for (int i=0; i<listeners.length; i+=2) {
            if (listeners[i]==RTPL2capAdapterSenderListener.class) {
                ((RTPL2capAdapterSenderListener)listeners[i+1]).connectionDataError(evt);
            }
        }     
    }
    
    void fireGetNewTimeValueEvent(GetNewTimeValueEvent evt) {
        Object[] listeners = listenerList.getListenerList();
        for (int i=0; i<listeners.length; i+=2) {
            if (listeners[i]==RTPL2capAdapterSenderListener.class) {
                ((RTPL2capAdapterSenderListener)listeners[i+1]).getNewTimeValue(evt);
            }
        }     
    }
}

